﻿namespace AmazonS3PublishPlugin
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Xml;
    using System.Xml.Serialization;
    using Amazon.S3;
    using Amazon.S3.Model;
    using Amazon.S3.Transfer;
    using Amazon.S3.Util;
    using AmazonS3PublishPlugin.Helpers;
    using AmazonS3PublishPlugin.Properties;
    using Microsoft.Expression.Encoder;
    using Microsoft.Expression.Encoder.Plugins.Publishing;

    [EncoderPlugin("Amazon S3", "Enables publishing to Amazon S3")]
    public class Publish : PublishPlugin, INotifyPropertyChanged
    {
        private bool _canceled;

        private JobBase _currentJob;

        private UploadInfo _currentUpload;

        private double _progress;

        public Publish()
        {
            Data = new PublishData();
        }

        public PublishData Data { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public override object CreateStandardSettingsEditor()
        {
            return new StandardUI(this);
        }

        public override object CreateAdvancedSettingsEditor()
        {
            return null;
        }

        public override void LoadJobSettings(XmlReader reader)
        {
            try
            {
                Data = PublishData.Load(reader);
            }
            catch (InvalidOperationException e)
            {
                if (Data == null) Data = new PublishData();
                PublishingHost.WriteActivityLogEntry(e.Message, ErrorLogSeverity.Error);
            }
        }

        public override void SaveJobSettings(XmlWriter writer)
        {
            Data.Save(writer);
        }

        public override void OnJobLoad(JobBase job)
        {
            _currentJob = job;
        }

        public override void PerformPublish(string rootPath, string[] filesToPublish)
        {
            _canceled = false;
            _progress = 0;

            var filesPublished = new List<string>();

            using (TransferUtility transferUtility = Data.CreateTransferUtility())
            {
                foreach (string file in filesToPublish)
                {
                    if (_canceled)
                    {
                        return;
                    }

                    string bucketName = Data.BucketName;
                    var currentFile = new FileInfo(file);
                    double fileProgress = (double)currentFile.Length / Helper.GetTotalSize(filesToPublish) * 100;
                    string folderName = Data.UseJobOutputFolder ? PublishingHost.CurrentJob.JobId : Data.FolderName;
                    string fileName = currentFile.Name;
                    string key = Path.Combine(folderName, fileName).Replace(Path.DirectorySeparatorChar, '/');

                    bool published = false;
                    try
                    {
                        OnProgress(string.Format(CultureInfo.CurrentCulture, Resources.Publishing, fileName), _progress);

                        if (AmazonS3Helper.DoesS3ObjectExist(file, bucketName, key, transferUtility.S3Client))
                        {
                            published = true;
                            continue;
                        }

                        _currentUpload = new UploadInfo(
                            CreateUploadRequest(file, bucketName, key), currentFile, fileProgress);
                        transferUtility.Upload(_currentUpload.Request);
                        published = true;
                    }
                    catch (Exception e)
                    {
                        if (_canceled) return;

                        MessageBoxResult result =
                            PublishingHost.ShowMessageBox(
                                e.Message + Environment.NewLine + Resources.CancelPublishQuestion,
                                MessageBoxButton.YesNo,
                                MessageBoxImage.Error);
                        if (result == MessageBoxResult.Yes)
                        {
                            _canceled = true;
                        }
                    }
                    finally
                    {
                        _currentUpload = null;
                        _progress += fileProgress;
                        if (published)
                        {
                            filesPublished.Add(AmazonS3Helper.GetUrl(bucketName, key).AbsoluteUri);
                            PublishingHost.WriteActivityLogEntry(
                                string.Format(CultureInfo.CurrentCulture, Resources.PublishFileSucceeded, fileName),
                                ErrorLogSeverity.Info);
                        }
                        else
                        {
                            PublishingHost.WriteActivityLogEntry(
                                string.Format(CultureInfo.CurrentCulture, Resources.PublishFileFailed, fileName),
                                ErrorLogSeverity.Error);
                        }
                    }
                }
            }

            if (Data.OpenInBrowser)
            {
                string pageUrl = filesPublished.FirstOrDefault(
                    u => u.EndsWith("Default.html", StringComparison.OrdinalIgnoreCase));
                if (pageUrl != null)
                {
                    Process.Start(pageUrl);
                }
            }

            PublishingHost.ShowMessageBox(
                Resources.PublishSucceeded,
                MessageBoxButton.OK,
                MessageBoxImage.Information);
        }

        protected override void CancelPublish()
        {
            if (_canceled) return;
            _canceled = true;

            _currentUpload.Request.UploadProgressEvent -= OnUploadProgressEvent;
            _currentUpload.Request.InputStream.Close();

            base.CancelPublish();
        }

        public void ListBuckets()
        {
            Data.AuthenticateStatus = AuthenticateStatus.Authenticating;
            Task.Factory.StartNew(() =>
                {
                    AmazonS3Client client = Data.CreateClient();
                    try
                    {
                        Data.Buckets = client.ListBuckets().Buckets;
                        Data.AuthenticateStatus = AuthenticateStatus.Success;
                    }
                    catch (Exception e)
                    {
                        Data.AuthenticateStatus = AuthenticateStatus.Error;
                        PublishingHost.ShowMessageBox(e.Message, MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                });
        }

        public bool CanListBuckets()
        {
            return Data != null && Data.IsCredentialSet;
        }

        internal void UpdateCurrentJob()
        {
            if (_currentJob != PublishingHost.CurrentJob)
            {
                _currentJob = PublishingHost.CurrentJob;
                Data = new PublishData();
            }
        }

        private TransferUtilityUploadRequest CreateUploadRequest(string filePath, string bucketName, string key)
        {
            var request = new TransferUtilityUploadRequest() { InputStream = File.OpenRead(filePath) };
            request
                .WithBucketName(bucketName)
                .WithKey(key)
                .WithContentType(AmazonS3Util.MimeTypeFromExtension(Path.GetExtension(filePath)))
                .WithCannedACL(Data.MakePublic ? S3CannedACL.PublicRead : S3CannedACL.Private)
                .WithTimeout(int.MaxValue)
                .WithStorageClass(
                    Data.UseReducedRedundancyStorage
                        ? S3StorageClass.ReducedRedundancy
                        : S3StorageClass.Standard)
                .WithSubscriber(OnUploadProgressEvent);
            return request;
        }

        private void OnUploadProgressEvent(object sender, UploadProgressArgs e)
        {
            OnProgress(
                string.Format(CultureInfo.CurrentCulture, Resources.Publishing, _currentUpload.File.Name),
                _progress + (_currentUpload.Progress * e.PercentDone / 100));
        }
    }
}